#include "AzAbstractDataSource.h"


#include <QMetaProperty>

#include <QDebug>

#include "AzRootRecord.h"


//--------------------------------------------------------------AzDataSourceException-----------------------------------------------------------

/*!
   \class AzDataSourceException
   \bried Общий класс ошибок  AzAbstractDataSource.

   Предназначен для унификации ошибок от разных источников данных.
   Все ошибки специализированных классов должны отвечать ошибкам данного класса.
   Конечно, класс описывает общие ошибки, поэтому проблема может быть не всегда понятна. Однако наследуемые классы должны предоставлять "свою" информацию.
   За это отвечают методы nativeErrorCode и nativeErrorString, которые возвращают код и текст ошибок производных классов. Понятно, тип ошибок мы заранее знать
   не можем, поэтому коды ошибок соответствуют int, сами коды нужно смотреть в определениях производных классов. В любом случае 0 - отсутсвие ошибки

   Признак того, что источник открыт - присутствие метаданных, т.е. fields.size > 0

*/

/*!
 * \brief Конструктор
 * \param errCode - общая ошибка данных
 * \param nativeErrCode - ошибка данных, которая присуща данному источнику данных
 */

AzDataSourceException::AzDataSourceException(Error errCode,int nativeErrCode,const QString& nativeMessage):
    mNativeErrorMessage(nativeMessage),mErrorCode(errCode) ,mNativeErrorCode(nativeErrCode){
    mMessage = codeToMessage(errCode);
}

AzDataSourceException::AzDataSourceException(const AzDataSourceException& src):AzException(src) {
    copy(src);
}

AzDataSourceException& AzDataSourceException::operator =(const AzDataSourceException& src) {
    if (this ==&src)
        return *this;
    copy(src);
    return *this;
}

void AzDataSourceException::copy(const AzDataSourceException& src) {
    mErrorCode = src.mErrorCode;
    mNativeErrorCode = src.mNativeErrorCode;
    mNativeErrorMessage = src.mNativeErrorMessage;
}



QString AzDataSourceException::codeToMessage(Error errCode) {
    switch (errCode) {
    case NotOpenError:          return "Source not open";
    case AllreadyOpenError:     return "Source allready open";
    case ConnectionError:       return "Connection error";
    case OpenError:             return "Can't open source";
    case ReadOnlyError:         return "Source is read only";
    case WriteOnlyError:        return "Source is write only";
    case WrongDataFormatError:  return "Wrong data format";
    default:
        qFatal("AzDataSourceException: not define code error message");
    }
    return QString();
}


//--------------------------------------------------------------AzAbstractDataSource------------------------------------------------------------

/*!
  \class AzAbstractDataSource
  \brief Класс для получение данных из внешнего источника.

  Метаданные - 2 способа получения - из внешнего источника либо установка пользователем. Храняться в mFields.
  Если мы установили сами \sa setFields, но при этом запросили явное получение метаданных \sa Az::DataPacketFlag, пользовательские данные затираются




  Порядок работы в режиме постоянного соединения:
*/

/*!
    \var AzAbstractDataSource::mDataPacketFlags;
    Содержит тип отравляемых и получаемых данных (Метаданные, записи, дельту или их комбинации.
*/

/*!
 * \brief AzAbstractDataSource::internalOpen()
 *  Establish permanent connection (if possible) and read field info
 *
 */


/*!
 * \brief AzAbstractDataSource::close()
 *  close connection and free field info
 *
 */


/*!
 * \brief AzAbstractDataSource::isConnectReadOnly()
 *  return true, if connection readOnly, otherwise return false
 * \Note - if connection not open, return true
 * \sa open(), isReadOnly()
 *
 */



/*!
 * \brief AzAbstractDataSource::estabsilshConnection(bool readOnly)
 * Устанавливает соединение с источником данных. Источник данных должен существовать.
 */


/*!
 * \brief AzAbstractDataSource::AzAbstractDataSource Конструктор класса.
 * \param [in] dsFlags флаг записи данных
 * \param [in] parent владелец объекта
 * Конструктор наследуемого класса,в основном, должен скрывать \a propFlags и переопредялеть в своем конструкторе,
 *  т.к. он определяет постоянное поведение объекта.
 */
AzAbstractDataSource::AzAbstractDataSource(Az::DataPacketFlags dsFlags ,QObject *parent):QObject(parent),
    mDataPacketFlags(dsFlags),mReadOnly(false) {
}

/*!
 * \brief AzAbstractDataSource::~AzAbstractDataSource деструктор
 * Разрушает объект и освобождает все ресурсы.
 */
AzAbstractDataSource::~AzAbstractDataSource() {
}

/*!
 * \brief AzAbstractDataSource::open()
 *  Establish permanent connection (if flags() PermanentConnection ) and read field info.
 * Если соединение без "состояния", либо соединение установлено ничего не делает
 * \sa internalOpen()
 */

void AzAbstractDataSource::open(bool readOnly) {
    if (mFlags & !Az::PermanentConnection) {
        return;
    }
    if (isOpen())
        return;
    setReadOnly(readOnly);
    estabsilshConnection();
    internalOpen();
    mFields = readMetadata();
}

/*!
 * \brief AzAbstractDataSource::close
 * Закрывает источник  и освобождает ресурсы
 */
void AzAbstractDataSource::close() {
    if (mFlags & !Az::PermanentConnection) {
        return;
    }
    internalClose();
    mFields.clear();
}

/*!
 * \brief AzAbstractDataSource::writeData
 * \param [in] savedData Записываемые записи
 * \param [in,out] error возвращает ошибки с соответствующей записью. Если ошибка не привязана к записи, то указатель на 0
 * \param [in] maxErrors максимальное кол-во ошибок, после которого прекращается выполняться запрос. -1 - обработать все записи.
 * \return Возвращает кол-во записаных записей
 *  Не вызывает исключений
 *
 */
int AzAbstractDataSource::writeData(const AzRecord* savedData,AzDionisWriteError * error, int) {
    if (mFlags & Az::PermanentConnection) {
        Q_ASSERT_X(false,"writeData","TO DO");
    }
    //if no any metadata exist
    if (mFields.size() == 0 && !savedData->rootRec() && savedData->rootRec()->fields().size() == 0) { //no any metadata found
        if (error) {
            AzDataSourceException e = AzDataSourceException(AzDataSourceException::NoMetadataFoundError);
            AzRecord *rec = 0;
            error->append(qMakePair(e,rec));
            return 0;
        }
    }
    //assign metadata
    if (mFields.size() == 0) {
        mFields=savedData->rootRec()->fields();
    }
    setReadOnly(false);
    try {
        estabsilshConnection();
        internalOpen();
    }
    catch (AzDataSourceException e) {
        AzRecord *rec = 0;
        error->append(qMakePair(e,rec));
        return 0;
    }
    doBeforeWrite();
    if (dataPacketFlags() & Az::MetadataPacket)
        writeMetadata(mFields);
    int res = 0;
    if ((dataPacketFlags() & Az::DataPacket) || (dataPacketFlags() & Az::DeltaPacket))
        res = writeRecords(savedData);
    doAfterWrite();
    close();
    return res;
}



/*!
 * \brief AzAbstractDataSource::writeRecords
 * \param rec - Корневая запись, относительно которой будут записыватся данные
 * Рекурсивная функция, записывает всех потомков \a rec
 *
 */
int AzAbstractDataSource::writeRecords(const AzRecord* rec) {
    int recCount = rec->childCount();
    if (recCount <= 0)
        return 0;
    if (!(dataPacketFlags() & Az::DeltaPacket) && !rec->hasUnmodified()) //хотим записать только дату, но там только новые данные
        return 0;
    int res = 0;
    doBeforeWriteChildRecords(rec);
    emit beforeWriteChildRecords(rec);
    for (int i = 0; i < recCount;++i) {
        AzRecord *chaildRec = rec->chaild(i);
        if (!(dataPacketFlags() & Az::DeltaPacket) && (chaildRec->recordState() == Az::Inserted))  {//Только данные и запись новая
            continue;
        }
        if (!(dataPacketFlags() & Az::DataPacket) && (chaildRec->recordState() == Az::Unmodified) && !chaildRec->hasModified()) {//Только дельта и запись немодифицирована
            //если только дельта, то записываем немодфицированную записьдля идентефикации иерархии
            continue;
        }
        emit beforeWriteRecord(chaildRec);
        writeRecord(chaildRec);
        ++res;
        res += writeRecords(chaildRec);
        emit afterWriteRecord(chaildRec);
    }
    doAfterWriteChildRecords(rec);
    emit afterWriteChildRecords(rec);
    return res;
}



/*!
 * \brief AzAbstractDataSource::getRecords
 * \param [in,out] records Список записей
 * \param parent
 * \param packetSize
 * \return readed count records
 * Считывает (пока) все записи, не зависимо от параметра
 * Если метаданные заданы, использует их, в противном случае считывает.
 */
int AzAbstractDataSource::readData(AzRecord* records, const AzRecord* parentRecord,int packetSize) {
    if (mFlags & Az::PermanentConnection) {
        Q_ASSERT_X(false,"writeData","TO DO");
    }
    setReadOnly(true);
    estabsilshConnection();
    internalOpen();
    doBeforeRead();
    if (records->rootRec()->fields().size() == 0) { //если метаданных нет, пытаемся их считать
        mFields = readMetadata();
        records->rootRec()->fields() = mFields;
    } else {
        mFields = records->rootRec()->fields(); //у нас свои метаданные, будем использовать их
    }
    int res = readRecords(records,parentRecord,packetSize);

    doAfterRead();
    close();
    return res;
}

